package gptma.bo.services.employee;

import gptma.bo.auth.UserRoles;
import gptma.bo.dao.FetchInformation;
import gptma.bo.dao.employee.EmployeeDao;
import gptma.bo.dao.task.ImputationDao;
import gptma.bo.dao.task.TaskDao;
import gptma.bo.dao.task.TaskHistoryElementDao;
import gptma.bo.model.employee.Employee;
import gptma.bo.model.project.Project;
import gptma.bo.model.task.HistoryState;
import gptma.bo.model.task.Imputation;
import gptma.bo.model.task.Task;
import gptma.bo.model.task.TaskHistoryElement;
import gptma.bo.services.cra.ActivityReport;
import gptma.bo.services.exception.DayLimitReachedException;
import gptma.bo.util.GptmaUtils;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;
import javax.inject.Inject;

// we don't put mappedName="ejb/EmployeeServices" because it is ignored by Glassfish, because
// this EJB has a combination of LOCAL & REMOTE business interfaces. Instead, we leave it empty
// and check which JNDI name is assigned through the "browse JNDI tree" functionality in asadmin
@Stateless
@RolesAllowed({ UserRoles.ROLE_ADMIN, UserRoles.ROLE_CHIEF,
		UserRoles.ROLE_EMPLOYEE })
public class EmployeeServicesImpl implements EmployeeServices,
		EmployeeServicesRemote {

	@Inject
	private EmployeeDao employeeDao;

	@Inject
	private ImputationDao imputationDao;

	@Inject
	private TaskDao taskDao;

	@Inject
	private TaskHistoryElementDao taskHistoryElementDao;

	@Override
	public List<Employee> findAll() {
		return employeeDao.findAll();
	}

	@Override
	public float findConsolidatedCharge(Employee employee, Date date) {
		float ret = 0;

		List<Imputation> imps = imputationDao.findImputationsFromDates(
				employee, date, null, false);
		if (imps != null) {
			for (Imputation imputation : imps) {
				ret += imputation.getCharge();
			}
		}

		return ret;
	}

	@Override
	public Employee findEmployee(String trigram) {
		return employeeDao.findEmployee(trigram);
	}

	@Override
	public List<Imputation> findImputationsFromDates(Employee employee,
			Date startDate, Date endDate) {
		return imputationDao.findImputationsFromDates(employee, startDate,
				endDate, true);
	}

	@Override
	public ActivityReport generateActivityReport(Employee employee, int month,
			int year) {
		ActivityReport ret = new ActivityReport(employee, month, year);

		List<Map<Integer, List<Imputation>>> list = getMonthImputation(
				employee, month, year);
		ret.setAdminImputations(list.get(0));
		ret.setProjectsImputations(list.get(1));

		List<Task> adminTasks = taskDao.findAll(true);
		ret.setAdminTasks(adminTasks);

		List<Task> projectsTasks = taskDao.findAll(false);
		ret.setProjectsTasks(projectsTasks);

		Set<Project> projects = getActiveProjects(employee, month, year);
		ret.setProjects(projects);

		return ret;
	}

	@Override
	public List<Task> findAssignedTasks(Employee employee) {
		return employeeDao.findAssignedTasks(employee);
	}

	@Override
	public List<Task> findAssignedTasks(Employee employee,
			FetchInformation fetchInformation) {
		return employeeDao.findAssignedTasks(employee, fetchInformation);
	}

	@Override
	public long findAssignedTasksCount(Employee employee) {
		return employeeDao.findAssignedTasksCount(employee);
	}

	@Override
	public List<Project> findManagedProjects(Employee employee) {
		return employeeDao.findManagedProjects(employee);
	}

	private List<Map<Integer, List<Imputation>>> getMonthImputation(
			Employee employee, int month, int year) {
		List<Map<Integer, List<Imputation>>> ret = new ArrayList<Map<Integer, List<Imputation>>>(
				2);
		Map<Integer, List<Imputation>> imputsAdmin = new HashMap<Integer, List<Imputation>>();
		Map<Integer, List<Imputation>> imputsNotAdmin = new HashMap<Integer, List<Imputation>>();

		for (int i = 0; i < 32; i++) {
			imputsAdmin.put(i, new ArrayList<Imputation>());
			imputsNotAdmin.put(i, new ArrayList<Imputation>());
		}

		Date[] boundaries = GptmaUtils.getEnclosingMonth(month, year);
		Date monthStart = boundaries[0];
		Date monthEnd = boundaries[1];

		List<Imputation> monthImput = imputationDao.findImputationsFromDates(
				employee, monthStart, monthEnd, true);
		Calendar cal = Calendar.getInstance();
		int day = -1;
		for (Imputation imputation : monthImput) {
			cal.setTime(imputation.getDate());
			day = cal.get(Calendar.DATE);

			Task t = imputation.getTask();
			if (t.getAdministrative()) {
				imputsAdmin.get(day).add(imputation);
			} else {
				imputsNotAdmin.get(day).add(imputation);
			}
		}

		ret.add(imputsAdmin);
		ret.add(imputsNotAdmin);

		return ret;
	}

	private Set<Project> getActiveProjects(Employee employee, int month,
			int year) {
		Set<Project> ret = new HashSet<Project>();

		Date[] boundaries = GptmaUtils.getEnclosingMonth(month, year);
		Date monthStart = boundaries[0];
		Date monthEnd = boundaries[1];

		List<Imputation> imputs = imputationDao.findImputationsFromDates(
				employee, monthStart, monthEnd, true);

		for (Imputation imputation : imputs) {
			Task t = imputation.getTask();

			if (!t.getAdministrative()) {
				Project p = t.getAttachedProject();
				if (p != null) {
					ret.add(p);
				}
			}
		}

		return ret;
	}

	@Override
	public void createImputation(Imputation imputation, boolean finished)
			throws DayLimitReachedException {
		Date date = imputation.getDate();

		float consumateBefore = findConsolidatedCharge(
				imputation.getEmployee(), date);

		float total = consumateBefore + imputation.getCharge();
		if (consumateBefore + imputation.getCharge() > 1) {
			throw new DayLimitReachedException(date, total);
		} else {
			imputationDao.save(imputation);

			if (finished) {
				TaskHistoryElement element = new TaskHistoryElement();
				element.setTask(imputation.getTask());
				element.setState(HistoryState.TACHE_CLOSE);
				element.setDate(date);
				element.setComment(imputation.getComment());

				taskHistoryElementDao.save(element);
			}
		}
	}

	@Override
	public int createMassImputation(Imputation template, Date startDate,
			Date endDate) {
		// nb of days missed
		int ret = 0;

		Employee emp = template.getEmployee();
		float consumate = template.getCharge();

		Calendar end = Calendar.getInstance(Locale.FRANCE);
		end.setTime(endDate);
		end.set(Calendar.HOUR_OF_DAY, 12);
		end.set(Calendar.MINUTE, 5);
		Date endLimit = end.getTime();

		Calendar cal = Calendar.getInstance(Locale.FRANCE);
		cal.setTime(startDate);
		cal.set(Calendar.HOUR_OF_DAY, 12);

		int day;
		float consumateBefore;
		Date date = null;
		Imputation imputation;
		do {
			date = cal.getTime();
			day = cal.get(Calendar.DAY_OF_WEEK);

			switch (day) {
			case Calendar.MONDAY:
			case Calendar.TUESDAY:
			case Calendar.WEDNESDAY:
			case Calendar.THURSDAY:
			case Calendar.FRIDAY:
				consumateBefore = findConsolidatedCharge(emp, date);

				if (consumateBefore + consumate > 1) {
					ret++;
				} else {
					imputation = new Imputation();
					imputation.setTask(template.getTask());
					imputation.setEmployee(emp);
					imputation.setDate(date);
					imputation.setCharge(consumate);
					imputation.setEtc(template.getEtc());
					imputation.setComment(template.getComment());

					imputationDao.save(imputation);
				}

				break;

			case Calendar.SATURDAY:
			case Calendar.SUNDAY:
				// do nothing
				break;
			}

			cal.add(Calendar.DATE, 1);
		} while (date.before(endLimit));

		return ret;
	}

	@Override
	public void deleteImputation(Imputation imputation) {
		imputationDao.delete(imputation);
	}
}
